﻿using System.Collections.Generic;
using MugenInjection.Interface;

namespace MugenInjection.Scope
{
    /// <summary>
    ///     The instance will be singleton for the duration of the unit of work. In practice this means that each injector will contain one unit of service.
    /// </summary>
    public sealed class UnitOfWorkScopeLifecycle : ScopeLifecycleBase
    {
        #region Fields

        private readonly Dictionary<IInjector, object> _uowObjects = new Dictionary<IInjector, object>();

        #endregion

        #region Overrides of ScopeLifecycleBase

        /// <summary>
        ///     Resolves instance for the specified <see cref="IBindingContext" /> from scope.
        /// </summary>
        /// <returns>An instance of the service.</returns>
        protected override object GetObjectFromScope()
        {
            lock (Locker)
            {
                object service;
                if (!_uowObjects.TryGetValue(Context.BindingContext.CallInjector, out service))
                {
                    Context.BindingContext.CallInjector.Disposed += InjectorDisposed;
                    service = ResolveCurrentService();
                    _uowObjects.Add(Context.BindingContext.CallInjector, service);
                }
                return service;
            }
        }

        /// <summary>
        ///     Gets all created objects in the current scope.
        /// </summary>
        protected override IList<object> GetObjectsFromScope()
        {
            var objects = new object[_uowObjects.Values.Count];
            _uowObjects.Values.CopyTo(objects, 0);
            return objects;
        }

        /// <summary>
        ///     Clears the current scope.
        /// </summary>
        protected override void ClearScope()
        {
            foreach (var uowObject in _uowObjects)
                uowObject.Key.Disposed -= InjectorDisposed;
            _uowObjects.Clear();
        }

        #endregion

        #region Methods

        private void InjectorDisposed(IDisposableObject obj)
        {
            obj.Disposed -= InjectorDisposed;
            var injector = obj as IInjector;
            if (injector == null) return;
            lock (Locker)
            {
                object disposeObj;
                if (!_uowObjects.TryGetValue(injector, out disposeObj))
                    return;

                _uowObjects.Remove(injector);
                TryReleaseObjectWithNotification(disposeObj);
            }
        }

        #endregion
    }
}